#include <stdlib.h>
#include <stdio.h>
#include <nds.h>
#include <ctype.h>  // for tolower()

#include <string.h>

// common headers
#include "common.h"

// local headers
#include "slibs.h"
#include "binarylist.h"
#include "filelist.h"

#include "library.h"

typedef struct {    // the header in the file
    char magic[13];    // magic string
    u8 pad[3];        // padding

    void *dataStart;
    void *dataEnd;
    void *interworkStart;
    void *interworkEnd;
    void *gotStart;
    void *gotEnd;
    void *bssStart;
    void *bssEnd;

    void *init;
    void *query;
    void *deinit;
} sLHeader;

void deinitLibInstance(sLibProc *lp, bool calllib);

char *getLPath(char *name) {
    if(!name) return NULL;

    static char path[256];

    snprintf(path, 256, "/data/SunOS/libraries/%s.sundll", name);

    return path;
}

void RelocateLibAdresses(void* buf) {
    s64 offset;
    u8 **address;
    u8 *_Start;
    u8 *_End;
    sLHeader *LH=(sLHeader *)buf;

    offset = (void *)LH - (void *)LH->dataStart;

    _Start = LH->dataStart;
    _End = LH->bssEnd;

    // Relocate pointers
    #define fixoffset(a) if(a) (a)+=offset
    fixoffset(LH->dataStart);
    fixoffset(LH->dataEnd);
    fixoffset(LH->interworkStart);
    fixoffset(LH->interworkEnd);
    fixoffset(LH->gotStart);
    fixoffset(LH->gotEnd);
    fixoffset(LH->bssStart);
    fixoffset(LH->bssEnd);

    fixoffset(LH->init);
    fixoffset(LH->query);
    fixoffset(LH->deinit);

#if 0    // Fix the interworking glue section
    for(address = (u8**)LH->interworkStart; address < (u8**)LH->interworkEnd; address++)
        if(_Start <= *address && *address < _End)
            *address += offset;

    // Fix the global offset table section (and the data between got and bss)
    for(address = (u8**)LH->gotStart; address < (u8**)LH->dataEnd; address++)
        if(_Start <= *address && *address < _End)
            *address += offset;
#else
u32 nr[3]={0,0,0};
    // Fix all
    for(address = (u8**)LH->dataStart; address < (u8**)LH->dataEnd; address++)
        if(_Start <= *address && *address < _End) {
            *address += offset;
if((u8 *)LH->interworkStart <= *address && *address < (u8 *)LH->interworkEnd) nr[0]++;
if((u8 *)LH->gotStart <= *address && *address < (u8 *)LH->gotEnd) nr[1]++;
if((u8 *)LH->bssStart <= *address && *address < (u8 *)LH->bssEnd) nr[2]++;
        }
/*iprintf(":%d(%d),%d(%d),%d(%d)",
    nr[0], LH->interworkEnd-LH->interworkStart,
    nr[1], LH->gotEnd-        LH->gotStart,
    nr[2], LH->bssEnd-        LH->bssStart
);*/

/*scanKeys();
while(!(keysDown() & KEY_B)) scanKeys();
scanKeys();*/

#endif
    #undef fixoffset
}

int initLibInstance(sLibProc *lp) {
    if(!lp || !lp->parent) return 1;

    size_t szb, szf, ret;
    void *buf;
    sLHeader *LH;

// <-- open file
    FILE *fd=fopen(getLPath(lp->parent->name), "rb");
    if(!fd) return 1;
// -->

// <-- get the full size with the bss section
    {
        sLHeader _LH;

        ret=fread((void *)&_LH, 1, sizeof(sLHeader), fd);
        if(ret!=sizeof(sLHeader)) {
            fclose(fd);
            return 1;
        }

        if(strncmp(_LH.magic, "SunOS_library", 13)) {    // not a valid library
            fclose(fd);
            return 1;
        }

        szb=_LH.bssEnd - _LH.dataStart;
    }
// -->

// allocate enough memory (data + bss)
    buf=xmalloc(szb);

// <-- get file content
    fseek(fd, 0, SEEK_END);
    szf=ftell(fd);
    rewind(fd);

    ret=fread(buf, 1, szf, fd);
    fclose(fd);
    if(ret!=szf) {
        xfree(buf);
        return 1;
    }
// -->

// relocate adresses
    RelocateLibAdresses(buf);

    LH=(sLHeader *)buf;

// Initialise the BSS to 0
    memset(LH->bssStart, 0, (u8*)LH->bssEnd - (u8*)LH->bssStart);

// copy info in the sLibrary structure
    lp->data=buf;
    lp->_init=LH->init;
    lp->query=LH->query;
    lp->_deinit=LH->deinit;

#if 0
{
char rr[256];
sprintf(rr, "/data/temp/%s.sundll.dump", lp->parent->name);
FILE *f=fopen(rr, "w+");
if(f) {
fwrite(buf, 1, szf, f);
fclose(f);
}
}
#endif

    DC_FlushAll();
    IC_InvalidateAll();

// call the init function
    if(lp->_init(&_libs)) {
        deinitLibInstance(lp, false);
        return 1;
    }

    return 0;
}

sLibResult getLibraryByFileExt(char *fn, enum LibraryType type) {
    sLibResult ret;
    bzero(&ret, sizeof(sLibResult));

    if(!fn) return ret;

    LLIST *cur=(type && type<LIB_NUM_TYPES)?liblists.lists[type]:liblists.all;
    int i;
    while(cur) {                                        // for each library in the list
        for(i=0; i<SL(cur)->exts.extnum; i++)           // for each extension in the list
            if(!cmpext(fn, SL(cur)->exts.exts[i])) {    // if good extension
                ret.lib=SL(cur);
                ret.extid=i;
                return ret;
            }

        cur=cur->next;
    }
    return ret;
}

sLibResult getLibraryByFileType(char *fn, enum LibraryType type) {   // filename without ext
    sLibResult ret;
    bzero(&ret, sizeof(sLibResult));

    if(!fn) return ret;

    size_t fnl=0;
    char *dn;   // dirname (with ending /)
    size_t dnl;
    char *bn=NULL;  // base name
    size_t bnl;

// <-- get dirname and basename
    char *p=fn;
    while(*p) {
        if(*p=='/') bn=p+1;
        fnl++;
        p++;
    }

    if(!bn) return ret;

    bnl=fnl-(bn-fn);
    dnl=fnl-bnl;
    dn=xmemdup(fn, dnl+1);
    dn[dnl]=0;
// -->

//iprintf("\ndn=%s(%d)", dn, dnl);
//iprintf("\nbn=%s(%d)", bn, bnl);

// <-- create the hash list for each extension
    typedef struct {
        char *ext;
        u32 hash;
        sLibrary *lib;
        int extid;
    } sHLT;

    int i;
    char efn[256];
    strcpy(efn, bn);
    sDA array;
    da_init(&array, 0, sizeof(sHLT));
    sHLT cel;

    LLIST *cur=type<LIB_NUM_TYPES?liblists.lists[type]:liblists.all;    // search in all libraries
    while(cur) {
        for(i=0; i<SL(cur)->exts.extnum; i++) {
            cel.extid=i;
            cel.ext=SL(cur)->exts.exts[i];
            strcpy(efn+bnl, cel.ext);
            cel.hash=fl_hash(efn);
            cel.lib=SL(cur);

//iprintf("'%s:%#x", efn, cel.hash);
            da_add(&array, &cel);
        }

        cur=cur->next;
    }
// -->

// scan the directory
    sFile *cf=NULL;//fl_get(dn, 0);    // (fast, doesn't stat files)
    xfree(dn);
    if(!cf) {
        da_free(&array);
        return ret;
    }
    while(cf) { // current file
        if(tolower(cf->name[0])==tolower(bn[0]))
            for(i=0; i<array.ne; i++)
                if(
                    ((sHLT *)array.data)[i].hash==cf->nameh &&
                    !strncasecmp(bn, cf->name, bnl) &&
                    !strcasecmp(cf->name+bnl, ((sHLT *)array.data)[i].ext)
                ) {
                    ret.lib=((sHLT *)array.data)[i].lib;
                    ret.file=cf;
                    ret.extid=((sHLT *)array.data)[i].extid;

                    da_free(&array);

                    return ret;
                }

        cf=cf->next;
    }

    da_free(&array);

    return ret;
}

sLibResult getLibraryByFileType_cfs(sFile *cf, char *bn, enum LibraryType type) {   // filename without ext
    sLibResult ret;
    bzero(&ret, sizeof(sLibResult));

    if(!cf /* TODO : if NULL, handle current working directory */ || !bn) return ret;

    size_t fnl=0;
    size_t dnl;
    size_t bnl=strlen(bn);

// <-- create the hash list for each extension
    typedef struct {
        char *ext;
        u32 hash;
        sLibrary *lib;
        int extid;
    } sHLT;

    int i;
    char efn[256];
    strcpy(efn, bn);
    sDA array;
    da_init(&array, 0, sizeof(sHLT));
    sHLT cel;

    LLIST *cur=type<LIB_NUM_TYPES?liblists.lists[type]:liblists.all;    // search in libraries
    while(cur) {
        for(i=0; i<SL(cur)->exts.extnum; i++) {
            cel.extid=i;
            cel.ext=SL(cur)->exts.exts[i];
            strcpy(efn+bnl, cel.ext);
            cel.hash=fl_hash(efn);
            cel.lib=SL(cur);

//iprintf("'%s:%#x", efn, cel.hash);
            da_add(&array, &cel);
        }

        cur=cur->next;
    }
// -->

// scan the directory
    while(cf) { // current file
        if(tolower(cf->name[0])==tolower(bn[0]))    // first quick test
            for(i=0; i<array.ne; i++)
                if(
                    ((sHLT *)array.data)[i].hash==cf->nameh &&
                    !strncasecmp(bn, cf->name, bnl) &&
                    !strcasecmp(cf->name+bnl, ((sHLT *)array.data)[i].ext)
                ) {
                    ret.lib=((sHLT *)array.data)[i].lib;
                    ret.file=cf;
                    ret.extid=((sHLT *)array.data)[i].extid;

                    da_free(&array);

                    return ret;
                }

        cf=cf->next;    // next file
    }

    da_free(&array);    // free dynamic hash/library array

    return ret;
}

sLibResult getLibraryById(u32 id) {
    sLibResult ret;
    bzero(&ret, sizeof(sLibResult));

    LLIST *cur=ID2TYPE(id)<LIB_NUM_TYPES?liblists.lists[ID2TYPE(id)]:liblists.all;    // search in all libraries
    while(cur) {
        if(SL(cur)->id==id) {
            ret.lib=SL(cur);
            return ret;
        }

        cur=cur->next;
    }
    return ret;
}

char *getExtByLibResult(sLibResult *lr) {
    if(!lr || !lr->lib || !lr->lib->exts.exts) return NULL;

    return lr->lib->exts.exts[lr->extid];
}

sLibProc *newLibInstance(sLibrary *lib) {
    if(!lib) return NULL;

    sLibProc lp;
    bzero(&lp, sizeof(sLibProc));
    lp.parent=lib;
    if(initLibInstance(&lp)) return NULL;

    if(lib->procs) {
        lib->procs->next=(sLibProc *)xmemdup(&lp, sizeof(sLibProc));

        lib->procs->next->prev=lib->procs;
        lib->procs=lib->procs->next;
    }
    else lib->procs=(sLibProc *)xmemdup(&lp, sizeof(sLibProc));

    return lib->procs;
}

void deinitLibInstance(sLibProc *lp, bool calllib) {
    if(!lp) return;

    if(lp->_deinit && calllib) lp->_deinit();
    if(lp->data) xfree(lp->data);
    lp->data=NULL;
}

void deleteLibInstance(sLibProc *lp) {
    sLibrary *lib;
    if(!lp || !(lib=lp->parent)) return;

    deinitLibInstance(lp, true);

    if(lp==lib->procs) {
        lib->procs=lp->prev;
    }
    else {
        lp->prev->next=lp->next;
        lp->next->prev=lp->prev;
    }
    xfree(lp);
}
